package br.com.games.hefesto.ai;

import java.util.Collections;
import java.util.List;

import br.com.games.hefesto.Game;
import br.com.games.hefesto.Move;
import br.com.games.hefesto.Player;
import br.com.games.hefesto.State;
import br.com.games.hefesto.Team;

/**
 * Buscador para melhor jogada
 * 
 * @param <Holder> Tipo usado para manter o estado do jogo
 * @param <Moveable> Objeto que pode ser lancado durante o jogo
 * 
 * @author <a href="mailto:fernando.schindler@gmail.com">Fernando Schindler</a>
 */
public abstract class Atena<Holder extends State, Moveable> implements Player<Holder, Moveable> {
	/**
	 * Criar um novo jogador
	 * 
	 * @see {@link Player}
	 * @param level
	 */
	public Atena(Team team, int level) {
		this.level = level;
		this.team = team;
	}
	
	/**
	 * 
	 * @param game
	 * @param level
	 * @return {@link Move}
	 */
	private Move<Moveable> find(Game<Holder, Moveable> game, int level) {

		Move<Moveable> best = null;
		Player<Holder, Moveable>  player = game.getCurrent();
		List<Move<Moveable>> valids = player.getValidMoves(game);

		for (Move<Moveable> m : valids) {
			try {
				if (m.isVictory()) {
					break; /* vitory cut-off */
				} else {
					game.perform(m);
					m.setWeight(alphabeta(game, player, Short.MIN_VALUE,
							Short.MAX_VALUE, (short) level));
					game.undo(m);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		Collections.sort(valids);

		best = valids.get(0);
		int max = valids.size();
		int idx = 1;

		/**
		 * Selects a random move between the best ones
		 */
		for (int i = 1; i < max; i++) {
			if (valids.get(i).getWeight() == best.getWeight()) {
				idx++;
			}
		}

		best = valids.get(((int) (Math.random() * 1000)) % idx);

		return best;
	}

	/**
	 * Implements alpha-beta pruning
	 * 
	 * @param game
	 * @param max
	 * @param alpha
	 * @param beta
	 * @param level
	 * @return Peso do estado atual
	 */
	private short alphabeta(Game<Holder, Moveable>  game, Player<Holder, Moveable> max, short alpha,
			short beta, short level) {
		Short result = null;
		Short value = null;
		Player<Holder, Moveable> player = game.getCurrent();

		if (null == value) {
			value = 0;
		}

		List<Move<Moveable>> valids = player.getValidMoves(game);
		Collections.sort(valids);

		if (level == 0 || valids.isEmpty()) {
			result = max.evaluate(game);
		} else {

			for (Move<Moveable> m : valids) {
				if (m.isVictory()) {
					value = max.evaluate(game);
				} else {
					try {
						game.perform(m);
						value = alphabeta(game, max, alpha, beta,
								(short) (level - 1));
						game.undo(m);
					} catch (Exception e) {
						value = alpha;
						e.printStackTrace();
					}
				}

				if (max.getTeam().equals(player.getTeam())) {
					alpha = (short) Math.max(alpha, value);
					if (alpha >= beta) {
						result = beta;
						break;
					}
				} else {
					beta = (short) Math.min(beta, value);
					if (alpha >= beta) {
						result = alpha;
						break;
					}
				}

				if (player.giveUp())
					break;
			}

			if (null == result)
				result = (max.getTeam().equals(player.getTeam())) ? alpha
						: beta;
		}

		if (null == result)
			result = 0;

		return result;
	}

	
    /*
     * (non-Javadoc)
     * @see br.com.games.hefesto.Player#getMove(br.com.games.hefesto.Game)
     */
	@SuppressWarnings("unchecked")
	@Override
	public Move<Moveable> getMove(Game<? extends Holder, Moveable> game) {
		long s = System.currentTimeMillis();
		Move<Moveable> result = null;
		State state = game.getState();
		state.setAnalyzeMode(true);
		result = find((Game<Holder, Moveable>) game, level);
		state.setAnalyzeMode(false);
		System.err.println(this + ":" + result + "(" + (System.currentTimeMillis()-s)+")");
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.com.games.hefesto.Player#getTeam()
	 */
	@Override
	public Team getTeam() {
		return this.team;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.com.games.hefesto.Player#getName()
	 */
	@Override
	public String getName() {
		return getClass().getSimpleName();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.com.games.hefesto.Player#getDescription()
	 */
	@Override
	public String getDescription() {
		return String.format("%s rate %d", getName(), level*100);
	}
	
	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return getDescription() + " as " + team;
	}

	/**
	 * Campos privados
	 */
	protected int level;
	private Team team;
}
